package org.example.uhy.application.service.impl;

import org.example.uhy.application.context.ServiceContext;
import org.example.uhy.application.converter.CouponConverter;
import org.example.uhy.application.dto.CouponArchiveInfo;
import org.example.uhy.application.dto.CouponInfo;
import org.example.uhy.application.dto.CreateCouponArchiveDto;
import org.example.uhy.application.dto.MemberQueryCondition;
import org.example.uhy.application.service.CouponService;
import org.example.uhy.domain.model.base.Operator;
import org.example.uhy.domain.model.base.TimeRange;
import org.example.uhy.domain.model.coupon.*;
import org.example.uhy.domain.model.fans.FansId;
import org.example.uhy.domain.model.marketing.ActivityId;
import org.example.uhy.domain.model.member.*;
import org.example.uhy.domain.model.trade.TradeId;
import org.example.uhy.exceptions.EntityNotFoundException;
import org.example.uhy.exceptions.IllegalParameterException;
import org.example.uhy.supports.meta.ApplicationService;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 卡券应用层服务实现类，提供粗粒度服务接口，并且处理事务
 * <p>
 * 领域模型中不建议使用{@link Autowire}，而应用层中可以使用，即与具体的某些技术结合
 */
@Service
@ApplicationService
public class CouponServiceImpl implements CouponService {
    @Autowired
    CouponRepository couponRepository;
    @Autowired
    CouponArchiveRepository archiveRepository;
    @Autowired
    MemberRepository memberRepository;
    @Autowired
    CouponConverter couponConverter;
    // 资格检查领域服务
    CouponAdmissionService admissionService = new CouponAdmissionService();

    @Transactional
    @Override
    public List<String> giveCouponsByCondition(String archiveId, String operatorId, MemberQueryCondition condition, ServiceContext context) {
        List<Member> members = memberRepository.members(MemberLabel.of(condition.getLabel()), MemberLevelId.of(condition.getLevel()));
        if (members.size() > 0) {
            List<String> memberIds = members.stream().map(member -> member.memberId().getId()).collect(Collectors.toList());
            return giveCouponsToMembers(archiveId, operatorId, memberIds, context);
        } else {
            return new ArrayList<>();
        }
    }

    @Transactional
    @Override
    public List<String> giveCouponsToMembers(String archiveId, String operatorId, List<String> memberIds, ServiceContext context) {
        if (memberIds == null || memberIds.size() == 0) {
            throw new IllegalParameterException("请传入会员ID");
        }
        List<CouponOwner> couponOwners = memberIds.stream().map(memberId -> CouponOwner.of(MemberId.of(memberId))).collect(Collectors.toList());
        return doGiveCoupons(CouponArchiveId.of(archiveId), Operator.of(operatorId), couponOwners, context);
    }

    @Transactional
    @Override
    public List<String> giveCouponsToFans(String archiveId, String operatorId, List<String> fansIds, ServiceContext context) {
        if (fansIds == null || fansIds.size() == 0) {
            throw new IllegalParameterException("请传入粉丝ID");
        }
        List<CouponOwner> couponOwners = fansIds.stream().map(fansId -> CouponOwner.of(FansId.of(fansId))).collect(Collectors.toList());
        return doGiveCoupons(CouponArchiveId.of(archiveId), Operator.of(operatorId), couponOwners, context);
    }

    private List<String> doGiveCoupons(CouponArchiveId archiveId, Operator operator, List<CouponOwner> couponOwners, ServiceContext context) {
        Optional<CouponArchive> fetchedArchive = archiveRepository.couponArchiveOfId(archiveId);
        if (!fetchedArchive.isPresent()) {
            throw new EntityNotFoundException("卡券定义不存在");
        }
        CouponArchive archive = fetchedArchive.get();
        List<String> couponIds = new ArrayList<>();
        for (CouponOwner couponOwner : couponOwners) {
            // 获取卡券授权检查
            Supplier<Integer> receivedQuantitySupplier = () -> couponRepository.countOfOwner(archiveId, couponOwner);
            admissionService.acquireAdmission(archive.constraint(), receivedQuantitySupplier, couponOwner);

            Coupon coupon = archive.generateCoupon(couponRepository.nextIdentity());
            coupon.giveTo(CouponSourceId.EMPTY, operator, couponOwner);
            couponRepository.add(coupon);

            couponIds.add(coupon.couponId().getId());
        }
        archiveRepository.save(archive);
        return couponIds;
    }

    @Transactional
    @Override
    public String receiveCouponByMember(String archiveId, String activityId, String memberId, ServiceContext context) {
        CouponId couponId = doReceiveCoupon(CouponArchiveId.of(archiveId), CouponSourceId.of(ActivityId.of(activityId)), CouponOwner.of(MemberId.of(memberId)), context);
        return couponId.getId();
    }

    @Transactional
    @Override
    public String receiveCouponByFans(String archiveId, String activityId, String fansId, ServiceContext context) {
        CouponId couponId = doReceiveCoupon(CouponArchiveId.of(archiveId), CouponSourceId.of(ActivityId.of(activityId)), CouponOwner.of(FansId.of(fansId)), context);
        return couponId.getId();
    }

    @Transactional
    @Override
    public String awardCoupon(String archiveId, String activityId, String memberId, ServiceContext context) {
        CouponId couponId = doReceiveCoupon(CouponArchiveId.of(archiveId), CouponSourceId.of(ActivityId.of(activityId)), CouponOwner.of(MemberId.of(memberId)), context);
        return couponId.getId();
    }

    @Transactional
    @Override
    public String buyCoupon(String archiveId, String orderId, String memberId, ServiceContext context) {
        CouponId couponId = doReceiveCoupon(CouponArchiveId.of(archiveId), CouponSourceId.of(OrderId.of(orderId)), CouponOwner.of(MemberId.of(memberId)), context);
        return couponId.getId();
    }

    private CouponId doReceiveCoupon(CouponArchiveId archiveId, CouponSourceId sourceId, CouponOwner owner, ServiceContext context) {
        Optional<CouponArchive> fetchedArchive = archiveRepository.couponArchiveOfId(archiveId);
        if (!fetchedArchive.isPresent()) {
            throw new EntityNotFoundException("卡券定义不存在");
        }
        CouponArchive archive = fetchedArchive.get();
        // 获取卡券授权检查
        Supplier<Integer> receivedQuantitySupplier = () -> couponRepository.countOfOwner(archiveId, owner);
        admissionService.acquireAdmission(archive.constraint(), receivedQuantitySupplier, owner);

        Coupon coupon = archive.generateCoupon(couponRepository.nextIdentity());
        coupon.receiveBy(sourceId, owner);
        couponRepository.add(coupon);
        archiveRepository.save(archive);
        return coupon.couponId();
    }

    @Transactional
    @Override
    public void consumeCoupon(String couponId, String memberId, ServiceContext context) {
        Optional<Coupon> fetchedCoupon = couponRepository.couponOfId(CouponId.of(couponId));
        if (!fetchedCoupon.isPresent()) {
            throw new EntityNotFoundException("卡券不存在");
        }
        Coupon coupon = fetchedCoupon.get();
        coupon.consumeBy(CouponOwner.of(MemberId.of(memberId)));
        couponRepository.save(coupon);
    }

    @Transactional
    @Override
    public void cancelCoupon(String couponId, String tradeId, String memberId, ServiceContext context) {
        Optional<Coupon> fetchedCoupon = couponRepository.couponOfId(CouponId.of(couponId));
        if (!fetchedCoupon.isPresent()) {
            throw new EntityNotFoundException("卡券不存在");
        }
        Coupon coupon = fetchedCoupon.get();
        CouponSourceId sourceId = CouponSourceId.of(TradeId.of(tradeId));
        coupon.cancelBy(sourceId, CouponOwner.of(MemberId.of(memberId)));
        couponRepository.save(coupon);
    }

    @Transactional
    @Override
    public void scheduleAvailableCoupons(ServiceContext context) {
        List<Coupon> coupons = couponRepository.scanExpiredCoupons();
        for (Coupon coupon : coupons) {
            coupon.expire();
            couponRepository.save(coupon);
        }
    }

    @Transactional
    @Override
    public void scheduleExpiredCoupons(ServiceContext context) {
        List<Coupon> coupons = couponRepository.scanExpiredCoupons();
        for (Coupon coupon : coupons) {
            coupon.expire();
            couponRepository.save(coupon);
        }
    }

    @Override
    public CouponInfo couponOfId(String id, ServiceContext context) {
        Optional<Coupon> fetchedCoupon = couponRepository.couponOfId(CouponId.of(id));
        return fetchedCoupon.isPresent() ? couponConverter.convert(fetchedCoupon.get()) : null;
    }

    @Transactional
    @Override
    public String addCouponArchive(CreateCouponArchiveDto archiveDto, String operatorId, ServiceContext context) {
        CouponArchive archive = new CouponArchive(
                archiveRepository.nextIdentity(),
                archiveDto.getName(),
                archiveDto.getDescription(),
                TimeRange.of(archiveDto.getEffectiveFromTime(), archiveDto.getEffectiveToTime()),
                archiveDto.getPlannedQuantity(),
                new CouponConstraint(archiveDto.getScope(), archiveDto.getCountPerOwner()),
                CouponParameter.ofParameters(archiveDto.getType(), archiveDto.getQuota(), archiveDto.getDecrease(), archiveDto.getDiscount(), archiveDto.getCounting()),
                Operator.of(operatorId)
        );
        archiveRepository.add(archive);
        return archive.archiveId().getId();
    }

    @Override
    public CouponArchiveInfo couponArchiveOfId(String archiveId, ServiceContext context) {
        Optional<CouponArchive> fetchedArchive = archiveRepository.couponArchiveOfId(CouponArchiveId.of(archiveId));
        return fetchedArchive.isPresent() ? couponConverter.convert(fetchedArchive.get()) : null;
    }
}
